package utils

import (
	"sync"
	"sync/atomic"
	"time"
)

type CacheMap struct {
	maxsz   int
	mutex   sync.RWMutex
	timeout time.Duration
	datamap map[any]*cacheitem
}

type cacheitem struct {
	times uint64
	etime time.Time
	value any
}

func (m *CacheMap) Clear() {
	m.mutex.Lock()
	m.datamap = nil
	m.mutex.Unlock()
}

func (m *CacheMap) Size() int {
	m.mutex.RLock()
	res := len(m.datamap)
	m.mutex.RUnlock()
	return res
}

func (m *CacheMap) Capacity() int {
	m.mutex.RLock()
	res := m.maxsz
	m.mutex.RUnlock()
	return res
}

func (m *CacheMap) Delete(key any) {
	m.mutex.Lock()
	delete(m.datamap, key)
	m.mutex.Unlock()
}

func (m *CacheMap) Init(maxsz int, timeout time.Duration) {
	m.mutex.Lock()
	m.datamap = make(map[any]*cacheitem)
	m.timeout = timeout
	m.maxsz = maxsz
	m.mutex.Unlock()
}

func NewCacheMap(maxsz int, timeout time.Duration) *CacheMap {
	var res CacheMap
	res.Init(maxsz, timeout)
	return &res
}

func (m *CacheMap) Set(key, value any) {
	var now = time.Now()
	var etime = now.Add(m.timeout)
	m.mutex.Lock()
	defer m.mutex.Unlock()
	if res, ok := m.datamap[key]; ok {
		res.etime = etime
		res.value = value
		if res.etime.Before(now) {
			res.times = 0
		}
	} else {
		var item = cacheitem{etime: etime, value: value}
		if m.datamap == nil {
			m.datamap = make(map[any]*cacheitem)
			m.timeout = time.Minute
			m.maxsz = 1000
		} else if len(m.datamap) >= m.maxsz {
			item.times = m.modify(now)
		}
		m.datamap[key] = &item
	}
}

func (m *CacheMap) Get(key any) (any, bool) {
	m.mutex.RLock()
	defer m.mutex.RUnlock()
	if res, ok := m.datamap[key]; ok {
		if res.etime.Before(time.Now()) {
			delete(m.datamap, key)
			return res.value, false
		}
		atomic.AddUint64(&res.times, 1)
		return res.value, true
	}
	return nil, false
}

func (m *CacheMap) Range(proc func(any, any) bool) int {
	var res = 0
	var now = time.Now()
	m.mutex.RLock()
	defer m.mutex.RUnlock()
	for k, v := range m.datamap {
		if v.etime.Before(now) {
			delete(m.datamap, k)
		} else if proc(k, v) {
			res++
		} else {
			break
		}
	}
	return res
}

func (m *CacheMap) modify(now time.Time) uint64 {
	var num int
	var del int
	var avg uint64
	for k, v := range m.datamap {
		if v.etime.Before(now) {
			delete(m.datamap, k)
			del++
		} else {
			avg += v.times
			num++
		}
	}
	if num == 0 {
		return 0
	}
	avg = avg / uint64(num)
	num = m.maxsz >> 3
	if num <= del {
		return avg
	}
	for k, v := range m.datamap {
		if num > del && v.times < avg {
			delete(m.datamap, k)
			v.value = nil
			num--
		}
	}
	if num > del {
		for k, v := range m.datamap {
			if num > del {
				delete(m.datamap, k)
				v.value = nil
				num--
			}
		}
	}
	return avg
}
